/*
 * Copyright (C) 2009 ZXing authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.tepth.maintenancesystem.view.zxingcapture.history;

import android.app.Activity;
import android.content.ContentValues;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.util.Log;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import com.tepth.maintenancesystem.view.zxingcapture.Intents;
import com.tepth.maintenancesystem.view.zxingcapture.PreferencesActivity;
import com.tepth.maintenancesystem.view.zxingcapture.result.ResultHandler;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * <p>
 * Manages functionality related to scan history.
 * </p>
 * 
 * @author Sean Owen
 */
public final class HistoryManager {

	private static final String TAG = HistoryManager.class.getSimpleName();

	private static final int MAX_ITEMS = 2000;

	private static final String[] COLUMNS = { DBHelper.TEXT_COL,
			DBHelper.DISPLAY_COL, DBHelper.FORMAT_COL, DBHelper.TIMESTAMP_COL,
			DBHelper.DETAILS_COL, };

	private static final String[] COUNT_COLUMN = { "COUNT(1)" };

	private static final String[] ID_COL_PROJECTION = { DBHelper.ID_COL };
	private static final String[] ID_DETAIL_COL_PROJECTION = { DBHelper.ID_COL,
			DBHelper.DETAILS_COL };

	private final Activity activity;

	public HistoryManager(Activity activity) {
		this.activity = activity;
	}

	public boolean hasHistoryItems() {
		SQLiteOpenHelper helper = new DBHelper(activity);
		SQLiteDatabase db = null;
		Cursor cursor = null;
		try {
			db = helper.getReadableDatabase();
			cursor = db.query(DBHelper.TABLE_NAME, COUNT_COLUMN, null, null,
					null, null, null);
			cursor.moveToFirst();
			return cursor.getInt(0) > 0;
		} finally {
			close(cursor, db);
		}
	}

	public List<HistoryItem> buildHistoryItems() {
		SQLiteOpenHelper helper = new DBHelper(activity);
		List<HistoryItem> items = new ArrayList<HistoryItem>();
		SQLiteDatabase db = null;
		Cursor cursor = null;
		try {
			db = helper.getReadableDatabase();
			cursor = db.query(DBHelper.TABLE_NAME, COLUMNS, null, null, null,
					null, DBHelper.TIMESTAMP_COL + " DESC");
			while (cursor.moveToNext()) {
				String text = cursor.getString(0);
				String display = cursor.getString(1);
				String format = cursor.getString(2);
				long timestamp = cursor.getLong(3);
				String details = cursor.getString(4);
				Result result = new Result(text, null, null,
						BarcodeFormat.valueOf(format), timestamp);
				items.add(new HistoryItem(result, display, details));
			}
		} finally {
			close(cursor, db);
		}
		return items;
	}

	public HistoryItem buildHistoryItem(int number) {
		SQLiteOpenHelper helper = new DBHelper(activity);
		SQLiteDatabase db = null;
		Cursor cursor = null;
		try {
			db = helper.getReadableDatabase();
			cursor = db.query(DBHelper.TABLE_NAME, COLUMNS, null, null, null,
					null, DBHelper.TIMESTAMP_COL + " DESC");
			cursor.move(number + 1);
			String text = cursor.getString(0);
			String display = cursor.getString(1);
			String format = cursor.getString(2);
			long timestamp = cursor.getLong(3);
			String details = cursor.getString(4);
			Result result = new Result(text, null, null,
					BarcodeFormat.valueOf(format), timestamp);
			return new HistoryItem(result, display, details);
		} finally {
			close(cursor, db);
		}
	}

	public void deleteHistoryItem(int number) {
		SQLiteOpenHelper helper = new DBHelper(activity);
		SQLiteDatabase db = null;
		Cursor cursor = null;
		try {
			db = helper.getWritableDatabase();
			cursor = db.query(DBHelper.TABLE_NAME, ID_COL_PROJECTION, null,
					null, null, null, DBHelper.TIMESTAMP_COL + " DESC");
			cursor.move(number + 1);
			db.delete(DBHelper.TABLE_NAME,
					DBHelper.ID_COL + '=' + cursor.getString(0), null);
		} finally {
			close(cursor, db);
		}
	}

	public void addHistoryItem(Result result, ResultHandler handler) {
		// Do not save this item to the history if the preference is turned off,
		// or the contents are
		// considered secure.
		if (!activity.getIntent().getBooleanExtra(Intents.Scan.SAVE_HISTORY,
				true)
				|| handler.areContentsSecure()) {
			return;
		}

		SharedPreferences prefs = PreferenceManager
				.getDefaultSharedPreferences(activity);
		if (!prefs.getBoolean(PreferencesActivity.KEY_REMEMBER_DUPLICATES,
				false)) {
			deletePrevious(result.getText());
		}

		ContentValues values = new ContentValues();
		values.put(DBHelper.TEXT_COL, result.getText());
		values.put(DBHelper.FORMAT_COL, result.getBarcodeFormat().toString());
		values.put(DBHelper.DISPLAY_COL, handler.getDisplayContents()
				.toString());
		values.put(DBHelper.TIMESTAMP_COL, System.currentTimeMillis());

		SQLiteOpenHelper helper = new DBHelper(activity);
		SQLiteDatabase db = null;
		try {
			db = helper.getWritableDatabase();
			// Insert the new entry into the DB.
			db.insert(DBHelper.TABLE_NAME, DBHelper.TIMESTAMP_COL, values);
		} finally {
			close(null, db);
		}
	}

	public void addHistoryItemDetails(String itemID, String itemDetails) {
		// As we're going to do an update only we don't need need to worry
		// about the preferences; if the item wasn't saved it won't be udpated
		SQLiteOpenHelper helper = new DBHelper(activity);
		SQLiteDatabase db = null;
		Cursor cursor = null;
		try {
			db = helper.getWritableDatabase();
			cursor = db.query(DBHelper.TABLE_NAME, ID_DETAIL_COL_PROJECTION,
					DBHelper.TEXT_COL + "=?", new String[] { itemID }, null,
					null, DBHelper.TIMESTAMP_COL + " DESC", "1");
			String oldID = null;
			String oldDetails = null;
			if (cursor.moveToNext()) {
				oldID = cursor.getString(0);
				oldDetails = cursor.getString(1);
			}

			if (oldID != null) {
				String newDetails;
				if (oldDetails == null) {
					newDetails = itemDetails;
				} else if (oldDetails.contains(itemDetails)) {
					newDetails = null;
				} else {
					newDetails = oldDetails + " : " + itemDetails;
				}
				if (newDetails != null) {
					ContentValues values = new ContentValues();
					values.put(DBHelper.DETAILS_COL, newDetails);
					db.update(DBHelper.TABLE_NAME, values, DBHelper.ID_COL
							+ "=?", new String[] { oldID });
				}
			}

		} finally {
			close(cursor, db);
		}
	}

	private void deletePrevious(String text) {
		SQLiteOpenHelper helper = new DBHelper(activity);
		SQLiteDatabase db = null;
		try {
			db = helper.getWritableDatabase();
			db.delete(DBHelper.TABLE_NAME, DBHelper.TEXT_COL + "=?",
					new String[] { text });
		} finally {
			close(null, db);
		}
	}

	public void trimHistory() {
		SQLiteOpenHelper helper = new DBHelper(activity);
		SQLiteDatabase db = null;
		Cursor cursor = null;
		try {
			db = helper.getWritableDatabase();
			cursor = db.query(DBHelper.TABLE_NAME, ID_COL_PROJECTION, null,
					null, null, null, DBHelper.TIMESTAMP_COL + " DESC");
			cursor.move(MAX_ITEMS);
			while (cursor.moveToNext()) {
				String id = cursor.getString(0);
				Log.i(TAG, "Deleting scan history ID " + id);
				db.delete(DBHelper.TABLE_NAME, DBHelper.ID_COL + '=' + id, null);
			}
		} catch (SQLiteException sqle) {
			// We're seeing an error here when called in
			// CaptureActivity.onCreate() in rare cases
			// and don't understand it. First theory is that it's transient so
			// can be safely ignored.
			Log.w(TAG, sqle);
			// continue
		} finally {
			close(cursor, db);
		}
	}

	/**
	 * <p>
	 * Builds a text representation of the scanning history. Each scan is
	 * encoded on one line, terminated by a line break (\r\n). The values in
	 * each line are comma-separated, and double-quoted. Double-quotes within
	 * values are escaped with a sequence of two double-quotes. The fields
	 * output are:
	 * </p>
	 * 
	 * <ul>
	 * <li>Raw text</li>
	 * <li>Display text</li>
	 * <li>Format (e.g. QR_CODE)</li>
	 * <li>Timestamp</li>
	 * <li>Formatted version of timestamp</li>
	 * </ul>
	 */
	CharSequence buildHistory() {
		SQLiteOpenHelper helper = new DBHelper(activity);
		SQLiteDatabase db = null;
		Cursor cursor = null;
		try {
			db = helper.getWritableDatabase();
			cursor = db.query(DBHelper.TABLE_NAME, COLUMNS, null, null, null,
					null, DBHelper.TIMESTAMP_COL + " DESC");

			DateFormat format = DateFormat.getDateTimeInstance(
					DateFormat.MEDIUM, DateFormat.MEDIUM);
			StringBuilder historyText = new StringBuilder(1000);
			while (cursor.moveToNext()) {

				historyText.append('"')
						.append(massageHistoryField(cursor.getString(0)))
						.append("\",");
				historyText.append('"')
						.append(massageHistoryField(cursor.getString(1)))
						.append("\",");
				historyText.append('"')
						.append(massageHistoryField(cursor.getString(2)))
						.append("\",");
				historyText.append('"')
						.append(massageHistoryField(cursor.getString(3)))
						.append("\",");

				// Add timestamp again, formatted
				long timestamp = cursor.getLong(3);
				historyText
						.append('"')
						.append(massageHistoryField(format.format(new Date(
								timestamp)))).append("\",");

				// Above we're preserving the old ordering of columns which had
				// formatted data in position 5

				historyText.append('"')
						.append(massageHistoryField(cursor.getString(4)))
						.append("\"\r\n");
			}
			return historyText;
		} finally {
			close(cursor, db);
		}
	}

	void clearHistory() {
		SQLiteOpenHelper helper = new DBHelper(activity);
		SQLiteDatabase db = null;
		try {
			db = helper.getWritableDatabase();
			db.delete(DBHelper.TABLE_NAME, null, null);
		} finally {
			close(null, db);
		}
	}

	static Uri saveHistory(String history) {
		File bsRoot = new File(Environment.getExternalStorageDirectory(),
				"BarcodeScanner");
		File historyRoot = new File(bsRoot, "History");
		if (!historyRoot.exists() && !historyRoot.mkdirs()) {
			Log.w(TAG, "Couldn't make dir " + historyRoot);
			return null;
		}
		File historyFile = new File(historyRoot, "history-"
				+ System.currentTimeMillis() + ".csv");
		OutputStreamWriter out = null;
		try {
			out = new OutputStreamWriter(new FileOutputStream(historyFile),
					Charset.forName("UTF-8"));
			out.write(history);
			return Uri.parse("file://" + historyFile.getAbsolutePath());
		} catch (IOException ioe) {
			Log.w(TAG, "Couldn't access file " + historyFile + " due to " + ioe);
			return null;
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch (IOException ioe) {
					// do nothing
				}
			}
		}
	}

	private static String massageHistoryField(String value) {
		return value == null ? "" : value.replace("\"", "\"\"");
	}

	private static void close(Cursor cursor, SQLiteDatabase database) {
		if (cursor != null) {
			cursor.close();
		}
		if (database != null) {
			database.close();
		}
	}

}
